﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Ria;
using SoftSource.Common;
using SoftSource.Reactive;

namespace SoftSource.Ria
{
    /// <summary>
    /// Wraps an EntitySet and exposes functionality via IObservables for easy composition.
    /// It also raises notifications when changes have been submitted
    /// </summary>
    /// <typeparam name="T">The type of the entity contained in the EntitySet within the DomainContext</typeparam>
    public sealed class EntityWrapper<T> : IObservable<Unit> where T : Entity, new()
    {
        private EntitySet<T> entitySet;
        private Subject<Unit> updateSubject;

        public EntityWrapper(DomainContext context)
            : this(context, null)
        {
        }

        public EntityWrapper(DomainContext context, Func<EntityQuery<T>> defaultLoadQuery)
        {
            if (context == null)
                throw new ArgumentNullException("context");

            this.updateSubject = new Subject<Unit>();
            this.entitySet = context.EntityContainer.GetEntitySet<T>();
            
            // If the default load query has been provided, allow the Refresh method to invoke that call.
            // Otherwise, provide a default IObservable to simulate the refresh call
            if (defaultLoadQuery != null)
                this.Refresh = context.Execute(defaultLoadQuery.TryInvoke());
            else
                this.Refresh = Observable.Return(new Unit());

            // The Load operation is Refresh that can only be executed once, but subscribed to n-times
            this.Load = this.Refresh.Once();
            this.Save = this.CreateSave(context);
            this.Reject = context.RejectChanges;

            this.Add = t => this.entitySet.Add(t);
            this.AddNew = () =>
                {
                    T t = new T();
                    this.entitySet.Add(t);
                    return t;
                };
            this.Remove = t =>
                {
                    if (this.entitySet.Contains(t))
                        this.entitySet.Remove(t);
                };
        }

        private IObservable<SubmitOperation> CreateSave(DomainContext context)
        {
            IObservable<SubmitOperation> submit = null;
            ISerialDomainContext sdc = context as ISerialDomainContext;
            
            // If this is a SerialDomainContext, then use its save operation
            if (sdc != null)            
                submit = sdc.SaveChanges();            
            else            
                submit = context.SaveChanges();            

            return submit.Do(operation => this.updateSubject.OnNext(new Unit()));
        }

        #region Properties
        /// <summary>
        /// Saves the changes made to the current EntitySet
        /// </summary>
        public IObservable<SubmitOperation> Save { get; private set; }

        /// <summary>
        /// Rejects the changes made to the current EntitySet
        /// </summary>
        public Action Reject { get; private set; }
        
        /// <summary>
        /// Adds a new entity to the current EntitySet
        /// </summary>
        public Func<T> AddNew { get; private set; }

        /// <summary>
        /// Adds a specific entity to the current EntitySet
        /// </summary>
        public Action<T> Add { get; private set; }

        /// <summary>
        /// Removes a specific entity from the current EntitySet
        /// </summary>
        public Action<T> Remove { get; private set; }

        /// <summary>
        /// The entities stored in the underlying EntitySet
        /// </summary>
        public IEnumerable<T> Entities
        {
            get { return this.entitySet; }
        }

        /// <summary>
        /// The default load query
        /// </summary>
        public IObservable<Unit> Refresh { get; private set; }

        /// <summary>
        /// The default load query that can only be executed once.
        /// </summary>
        public IObservable<Unit> Load { get; private set; }
        #endregion Properties

        #region IObservable<Unit> Members

        public IDisposable Subscribe(IObserver<Unit> observer)
        {
            return this.updateSubject.Subscribe(observer);
        }

        #endregion
    }
}
